/**  
  -- The Performance Driven Lime Project -- 

  A self adaptive tuple space for mobile agents and hosts by dynamic
  adapting tuple's replication policy.
  
  Copyright (C) 2008
  Xing Jiankuan, Pan Wenhui
  SAGroup of Tsinghua University

  This library is free software; you can redistribute it and/or
  modify it under the terms of the GNU Lesser General Public
  License as published by the Free Software Foundation; either
  version 2.1 of the License, or (at your option) any later version.

  This library is distributed in the hope that it will be useful,
  but WITHOUT ANY WARRANTY; without even the implied warranty of
  MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
  Lesser General Public License for more details.

  You should have received this copy of the GNU Lesser General Public
  License along with this library; if not, write to the Free Software
  Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA 
 */
package sagroup.dracots;

import java.io.ByteArrayInputStream;
import java.io.ByteArrayOutputStream;
import java.io.IOException;
import java.io.ObjectInputStream;
import java.io.ObjectOutputStream;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Map;
import java.util.Stack;

import sagroup.dracots.TupleType;
import lights.adapters.Field;
import lights.adapters.Tuple;
import lights.dracots.ClassNameField;
import lights.interfaces.ITuple;
import lights.interfaces.IValuedField;
import lime2.LimeTemplate;
import lime2.LimeTuple;
import lime2.LimeTupleSpace;
import lime2.LimeWrapper;
import lime2.Location;
import lime2.TupleID;

/**
 * <p>
 * DracoTS Tuple. This is an abstract class for extension for specialized used
 * tuple. This class does not differentiate tuple and template as
 * <code>LimeTuple</code> and <code>LimeTemplate</code>, but it provides methods
 * to generate these two kinds of instances to use Lime.
 * </p>
 * 
 * <p>
 * Tuple are categorized as "Signal Tuple" and "Share Tuple". A signal tuple is
 * designed to be transferred from one host to another, and then it is handled
 * and thrown away. So it should be as small as possible. See
 * {@link SignalTuple}.
 * </p>
 * 
 * <p>
 * A share tuple is used for data sharing. An agent can replicate it and cache
 * it in its own host. So share tuple provides more fields to support
 * replication. See {@link ShareTuple}.
 * </p>
 * 
 * <p>
 * This class implements Cloneable interface. In the {@link ShareTuple} the
 * replicate and duplicate methods will use clone() method. If no mutable fields
 * are involved, there is no necessary to override clone(). However, if some
 * mutable objects are used for tuple, you must add custom deep copy in override
 * clone() method by yourself to avoid the mess incurred by the references to
 * the identical object.
 * </p>
 * 
 * @author Xing Jiankuan
 * 
 */
public class DracoTuple implements java.io.Serializable, Cloneable {
	/**
	 * Tuple Category: Signal tuple
	 */
	public static final int SIGNAL = 1;

	/**
	 * Tuple Category: Share tuple
	 */
	public static final int SHARE = 2;

	private static final long serialVersionUID = 3148070996035733619L;

	LimeWrapper limeWrapper;

	/**
	 * Tuple's type, every tuple should belong to a type.
	 */
	TupleType type;

	/**
	 * If use an object generate a tuple, this is necessary for the object's
	 * reconstruction
	 */
	String clzName = this.getClass().getName();

	/**
	 * Tuple's version layer ID. This ID is different from a
	 * <code>LimeTuple</code> ID. For a tuple and its replicas, they have
	 * different <code>LimeTuple</code> ID, but have the same version layer ID.
	 */
	TupleID vid;

	boolean isMatchAll = false;
	
	transient int size = -1;

	/**
	 * An empty lime template, we need it because we must construct template.
	 */
	public static final LimeTemplate DUMMY_LT = new LimeTemplate(
			Location.UNSPECIFIED, Location.UNSPECIFIED, new Tuple());

	/**
	 * An wildcard tuple. It can be used as a template to match any fields. This
	 * tuple must be put in the tail so that it takes effect.
	 */
	public static final ITuple MATCHALL = new Tuple().add(new Field(
			lights.dracots.Field.MATCH_ALL_FIELD));

	/**
	 * An empty flat tuple
	 */
	static final ITuple DUMMY_TUPLE = new Tuple();

	/**
	 * Create a template which can match any DracoTuple objects or objects of
	 * DracoTuple's sub-class. You can change this template as you wish.
	 * 
	 * @author Xing Jiankuan
	 */
	public static DracoTuple createMatchAllTemplate() {
		DracoTuple t = new DracoTuple(null, Location.UNSPECIFIED,
				Location.UNSPECIFIED, null, null);
		t.setMatchAll(true);
		return t;
	}

	/**
	 * Create a template that can match all the local draco tuples. You also can
	 * add custom specified valued field to this template to construct custom
	 * template.
	 * 
	 * @return the template
	 * @author Xing Jiankuan
	 */
	public static DracoTuple createMatchAllLocalTemplate() {
		DracoTuple ret = createMatchAllTemplate();
		ret.limeWrapper.cur = Location.HERE;
		ret.limeWrapper.dest = Location.HERE;
		return ret;
	}

	/**
	 * Create a template that can match all draco tuples with specified
	 * destination. You also can add custom specified valued field to this
	 * template to construct custom template.
	 * 
	 * @param dest
	 * @return
	 * @author Xing Jiankuan
	 */
	public static DracoTuple createMatchAllRemoteTemplate(Location dest) {
		DracoTuple template = createMatchAllTemplate();
		template.limeWrapper.dest = dest;
		return template;
	}

	protected DracoTuple(TupleID tid, Location curr, Location dest,
			TupleType type, TupleID vid) {
		limeWrapper = new LimeWrapper(tid, curr, dest, DracoTuple.DUMMY_TUPLE);
		this.type = type;
		this.vid = vid;
	}

	protected DracoTuple() {
		// empty constructor
	}

	public TupleID getVID() {
		return vid;
	}

	public void setVID(TupleID vid) {
		this.vid = vid;
	}

	public TupleType getType() {
		return type;
	}

	public void setType(TupleType type) {
		this.type = type;
	}

	public LimeWrapper getLimeWrapper() {
		return limeWrapper;
	}

	/**
	 * Convert current DracoTuple instance to LimeTuple. All fields are
	 * flattened, and put into the result.
	 * 
	 * @return a lime tuple instance
	 * @author Xing Jiankuan
	 */
	public LimeTuple toLimeTuple() {
		ITuple t = this.flatten();
		return new LimeTuple(limeWrapper.id, limeWrapper.cur, limeWrapper.dest,
				t);
	}

	/**
	 * Convert current DracoTuple instance to LimeTemplate. All fields are
	 * flattened, and put into the result. This method is mainly used for
	 * <code>LimeTupleSpace.rd</code> series and <code>LimeTupleSpace.in
	 * </code> series.
	 * 
	 * @return a lime template instance
	 * @author Xing Jiankuan
	 */
	public LimeTemplate toLimeTemplate() {
		ITuple t = this.flatten();
		return new LimeTemplate(limeWrapper.id, limeWrapper.cur,
				limeWrapper.dest, t);
	}
	
	/**
	 * Convert current DracoTuple instance to LimeWrapper. All fields are
	 * flattened, and put into the result. This method is mainly used for
	 * <code>LimeTupleSpace.rd</code> series and <code>LimeTupleSpace.in
	 * </code> series.
	 * 
	 * @return a lime template instance
	 * @author Xing Jiankuan
	 */
	public LimeWrapper toLimeWrapper() {
		ITuple t = this.flatten();
		return new LimeWrapper(limeWrapper.id, limeWrapper.cur,
				limeWrapper.dest, t);
	}

	/**
	 * The factory method that create a DracoTuple instance from a Lime Wrapper.
	 * The real type of tuple is decided by t's content.
	 * 
	 * @param wrapper
	 * @return a DracoTuple object or null if input is null.
	 * @author Xing Jiankuan
	 * @throws ClassNotFoundException
	 *             thrown when object reconstruction is failed since cannot find
	 *             the matched class
	 * 
	 */
	public static DracoTuple create(LimeWrapper wrapper) {
		if (wrapper == null)
			return null;
		try {
			String clzName = (String) ((IValuedField) wrapper.tuple
					.get(DracoConstants.CLZ_TAG)).getValue();
			Class clz = Class.forName(clzName);
			DracoTuple instance = (DracoTuple) clz.newInstance();
			instance.createFromLimeWrapper(wrapper);
			return instance;
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		} catch (SecurityException e) {
			e.printStackTrace();
		} catch (IllegalArgumentException e) {
			e.printStackTrace();
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Accept a flat tuple that contain all fields including lime wrapper
	 * fields. The input flat tuple will be first delimified and forward to
	 * <code>
	 * create(LimeWrapper wrapper).
	 * 
	 * @return the result DracoTuple object or null if input is null
	 * @throws ClassNotFoundException
	 *             thrown when object reconstruction is failed since cannot find
	 *             the matched class
	 */
	public static DracoTuple create(ITuple t) throws ClassNotFoundException {
		if (t == null)
			return null;
		LimeWrapper lw = LimeWrapper.delimifyWrapper(t);
		return create(lw);
	}

	public Location getCurr() {
		return limeWrapper.cur;
	}

	public Location getDest() {
		return limeWrapper.dest;
	}

	public void setCurr(Location curr) {
		limeWrapper.cur = curr;
	}

	public void setDest(Location dest) {
		limeWrapper.dest = dest;
	}

	public String getClzName() {
		return clzName;
	}

	public void setClzNameFormal() {
		clzName = null;
	}

	public void resetClzName() {
		clzName = this.getClass().getName();
	}

	public void setMatchAll(boolean matchAll) {
		this.isMatchAll = matchAll;
	}

	/**
	 * Keep all the fields to map to tuple
	 */
	private static Map<String, LinkedList<java.lang.reflect.Field>> clzFieldsForTupleMap = new HashMap();

	public static LinkedList<java.lang.reflect.Field> getAllClzFieldsForTuple(
			Class clz) {
		synchronized (clzFieldsForTupleMap) {
			String originName = clz.getName();
			LinkedList<java.lang.reflect.Field> allClzFieldsForTuple = clzFieldsForTupleMap
					.get(clz.getName());
			if (allClzFieldsForTuple != null)
				return allClzFieldsForTuple;

			// lazy initialization
			// Traverse the inheritance chain of clz, and find all the fields
			// annotated by @Field
			Stack<LinkedList<java.lang.reflect.Field>> allClzFields = new Stack();
			while (clz != DracoTuple.class) {
				java.lang.reflect.Field[] fs = clz.getDeclaredFields();
				LinkedList<java.lang.reflect.Field> clzFields = new LinkedList();
				for (java.lang.reflect.Field f : fs) {
					// the list contain all the fields in one class
					if (f.isAnnotationPresent(sagroup.dracots.Field.class)) {
						if (f.getClass().isPrimitive())
							throw new RuntimeException(
									"Should not use primitive types for tuple field");
						f.setAccessible(true); // avoid the Java build-in
												// accessibility check
						clzFields.add(f);
					}
				}
				allClzFields.push(clzFields);
				clz = clz.getSuperclass();
			}

			LinkedList<java.lang.reflect.Field> ret = new LinkedList();
			while (!allClzFields.isEmpty()) {
				ret.addAll(allClzFields.pop());
			}

			clzFieldsForTupleMap.put(originName, ret);
			return ret;
		}
	}

	/**
	 * Fill fields of current <code>DracoTuple</code> fields by a flat tuple.
	 * will accept flat tuple without lime fields. So before using this method
	 * you should confirm this point.
	 * 
	 * @param flat
	 *            the flat tuple to be deserialized
	 * @author Xing Jiankuan
	 */
	protected void createFromFlat(ITuple flat) {
		LinkedList<java.lang.reflect.Field> allClzFieldsForTuple = getAllClzFieldsForTuple(this
				.getClass());

		IValuedField tupleField = null;

		tupleField = (IValuedField) flat.get(flat.length() - 1);// get the last
																// field
		if (tupleField.equals(lights.dracots.Field.MATCH_ALL_FIELD)) {
			// is a match-all template
			isMatchAll = true;

		} else {
			isMatchAll = false;
		}

		// read from flat tuples
		if (!allClzFieldsForTuple.isEmpty()) {
			for (int i = 0; i < allClzFieldsForTuple.size(); i++) {
				int index = i + DracoConstants.DRACO_TAGS_NUMBER;
				tupleField = (IValuedField) flat.get(index);
				try {
					if (tupleField.isFormal()) {
						if (tupleField.getType().equals(Location.class)) {
							allClzFieldsForTuple.get(i).set(this,
									Location.UNSPECIFIED);
						} else {
							allClzFieldsForTuple.get(i).set(this, null);
						}
					} else {
						allClzFieldsForTuple.get(i).set(this,
								tupleField.getValue());
					}
				} catch (IllegalArgumentException e) {
					throw e;
				} catch (IllegalAccessException e) {
					e.printStackTrace();
				}
			}
		}

		// read draco tuple fields
		tupleField = (IValuedField) flat.get(DracoConstants.TYPE_TAG);
		if (tupleField.isFormal())
			type = null;
		else
			type = (TupleType) tupleField.getValue();
		tupleField = (IValuedField) flat.get(DracoConstants.VID_TAG);

		if (tupleField.isFormal())
			vid = null;
		else
			vid = (TupleID) tupleField.getValue();

		tupleField = (IValuedField) flat.get(DracoConstants.CLZ_TAG);
		if (tupleField.isFormal())
			clzName = null;
		else
			clzName = (String) tupleField.getValue();

		customCreateFromFlat(flat);
	}

	/**
	 * Perform custom create from flat. Anyone who uses custom method to parse
	 * the flat should override this method.
	 * 
	 * @param flat
	 * @author Xing Jiankuan
	 */
	protected void customCreateFromFlat(ITuple flat) {

	}

	/**
	 * Put the system fields of Shared into a flat tuple. This tuple may contain
	 * all system fields, including LimeTuple's and ShareTuple's or Share's
	 * only. User choose which kind of tuple to be generated by giving the
	 * option. The result tuple is:<br>
	 * <table border="1" >
	 * <tr align="center">
	 * <td><b>Position</b></td>
	 * <td>0</td>
	 * <td>1</td>
	 * <td>2</td>
	 * </tr>
	 * <tr>
	 * <td><b>Field</b></td>
	 * <td>TupleType</td>
	 * <td>VID</td>
	 * <td>other data</td>
	 * </tr>
	 * </table>
	 * 
	 */
	protected ITuple flatten() {
		ITuple t = new Tuple();
		LinkedList<java.lang.reflect.Field> allClzFieldsForTuple = getAllClzFieldsForTuple(this
				.getClass());
		// first write DracoTuple fields
		if (type == null) {
			t.insertAt(new Field().setType(TupleType.class),
					DracoConstants.TYPE_TAG);
		} else {
			t.insertAt(new Field().setValue(type), DracoConstants.TYPE_TAG);
		}
		if (vid == null) {
			t.insertAt(new Field().setType(TupleID.class),
					DracoConstants.VID_TAG);
		} else {
			t.insertAt(new Field().setValue(vid), DracoConstants.VID_TAG);
		}
		if (clzName == null) {
			t.insertAt(new Field(new ClassNameField(this.isMatchAll))
					.setType(String.class), DracoConstants.CLZ_TAG);
		} else {
			t.insertAt(new Field(new ClassNameField(this.isMatchAll))
					.setValue(clzName), DracoConstants.CLZ_TAG);
		}

		// then write all remain @Field fields
		for (int i = 0; i < allClzFieldsForTuple.size(); i++) {
			int index = i + DracoConstants.DRACO_TAGS_NUMBER;
			java.lang.reflect.Field f = allClzFieldsForTuple.get(i);
			try {
				Object value = f.get(this);
				if (value == null || value.equals(Location.UNSPECIFIED)) {
					t.insertAt(new Field().setType(f.getType()), index);
				} else {
					t.insertAt(new Field().setValue(value), index);
				}
			} catch (IllegalArgumentException e) {
				e.printStackTrace();
			} catch (IllegalAccessException e) {
				e.printStackTrace();
			}
		}

		customFlatten(t);

		if (isMatchAll) {
			// insert the match-all wild card to the last field
			t.insertAt(new Field(lights.dracots.Field.MATCH_ALL_FIELD), t
					.length());
		}

		return t;
	}

	/**
	 * Perform custom create from flat. Anyone who uses custom method to parse
	 * the flat should override this method.
	 */
	protected void customFlatten(ITuple t) {

	}

	/**
	 * Fill fields of current <code>DracoTuple</code> fields by a lime wrapper.
	 * That is, extract fields in lime wrapper's user data tuple
	 * 
	 * @param wrapper
	 * @author Xing Jiankuan
	 */
	protected void createFromLimeWrapper(LimeWrapper wrapper) {
		this.limeWrapper = wrapper;
		createFromFlat(wrapper.tuple);
		wrapper.tuple = DracoTuple.DUMMY_TUPLE;
	}

	public String toString() {
		return String.format("type=%s;vid=%s;clz=%s;tid=%s;curr=%s;dest=%s",
				type, vid, clzName, limeWrapper.id.toString(), limeWrapper.cur
						.toString(), limeWrapper.dest.toString());
	}

	/**
	 * Convert current <code>DracoTuple</code> instance to a flat tuple. The
	 * result contains no <code>LimeWrapper</code> fields, and tends to be used
	 * in "out" series of methods. <code>LimeTupleSpace#out</code> method will
	 * add <code>LimeWrapper</code> fields.
	 * 
	 * @return
	 * @author Xing Jiankuan
	 */
	public ITuple toFlatTuple() {
		return flatten();
	}

	/**
	 * Facility to duplicate an exact copy of this tuple, including its vid.
	 * However, the copy's tid is different since it's a different tuple. And
	 * the copy's destination will be set as user specified.
	 * @param dest
	 *            the destination of duplicated tuple
	 * @return the exact copy
	 * 
	 * @author Huang Xiaodong
	 */
	public DracoTuple duplicate(Location dest) {
		DracoTuple ret = deepCopy();
		ret.limeWrapper = new LimeWrapper(LimeTupleSpace.nextTID(),
					ret.limeWrapper.cur, dest, DracoTuple.DUMMY_TUPLE);
		return ret;
	}

	/**
	 * Facility to duplicate an exact copy of this tuple as a template.
	 * And	the copy's destination will be set as user specified.
	 * 
	 * @param dest
	 * @return
	 * @author Xing Jiankuan
	 */
	public DracoTuple duplicateTemplate(Location dest) {
		DracoTuple ret = deepCopy();
		ret.limeWrapper = new LimeWrapper(this.limeWrapper.id,
				ret.limeWrapper.cur, dest, DracoTuple.DUMMY_TUPLE);
		return ret;
	}

	
	/**
	 * Generate a deep copy through serialization
	 * 
	 * @return
	 * @author Xing Jiankuan
	 */
	private DracoTuple deepCopy() {
		ByteArrayOutputStream bo = new ByteArrayOutputStream();
		ObjectOutputStream oo;
		try {
			oo = new ObjectOutputStream(bo);
			oo.writeObject(this);
			ByteArrayInputStream bi = new ByteArrayInputStream(bo.toByteArray());
			ObjectInputStream oi = new ObjectInputStream(bi);
			oo.close();
			oi.close();
			return (DracoTuple)oi.readObject();
		} catch (IOException e) {
			e.printStackTrace();
		} catch (ClassNotFoundException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Return the memory size used by this object. The size is estimated
	 * by objects' serialization byte array's length
	 * 
	 * @return size value in bytes.
	 * @author Xing Jiankuan
	 */
	public int getSize() {
		if (size == -1) {
			ByteArrayOutputStream bo = new ByteArrayOutputStream();
			ObjectOutputStream oo;
			try {
				oo = new ObjectOutputStream(bo);
				oo.writeObject(this);
				size = bo.toByteArray().length;
				oo.close();
			} catch (IOException e) {
				e.printStackTrace();
			} 
		}
		return size;
	}

	/**
	 * Create a template that can match any tuple of a specific tuple class and
	 * its sub-classes. That is, all the fields are "null" (Location fields are
	 * UNSPECIFIED)
	 * 
	 * @param clz
	 *            the specific tuple class object. It must be a subclass of
	 *            <code>DracoTuple</code>.
	 * @return the match all template to match the tuples whose is objects of
	 *         clz or clz's sub classes.
	 * 
	 * @author Xing Jiankuan
	 */
	public static DracoTuple createMatchAllTemplateByClass(Class clz) {
		DracoTuple template = createTemplateByClass(clz);
		template.setMatchAll(true);
		return template;
	}

	/**
	 * Create a template that can match any tuple of a specific tuple class.
	 * That is, all the fields are "null" (Location fields are UNSPECIFIED)
	 * 
	 * @param clz
	 *            the specific tuple class object. It must be a subclass of
	 *            <code>DracoTuple</code>.
	 * @return the match all template to match the tuples whose is objects of
	 *         clz.
	 * 
	 * @author Xing Jiankuan
	 */
	public static DracoTuple createTemplateByClass(Class clz) {
		if (!DracoTuple.class.isAssignableFrom(clz)) {
			throw new IllegalArgumentException("The specified class "
					+ "must be sub class of DracoTuple");
		}
		try {
			DracoTuple ret = (DracoTuple) clz.newInstance();

			// set DracoTS fields
			ret.clzName = clz.getName();
			ret.limeWrapper = new LimeTuple(null, Location.UNSPECIFIED,
					Location.UNSPECIFIED, DracoTuple.DUMMY_TUPLE);
			ret.type = null;
			ret.vid = null;

			// set custom fields
			LinkedList<java.lang.reflect.Field> allClzFieldsForTuple = getAllClzFieldsForTuple(clz);
			for (java.lang.reflect.Field f : allClzFieldsForTuple) {
				if (f.getType().equals(Location.class)) {
					// Location fields use special wildcard
					f.set(ret, Location.UNSPECIFIED);
				} else {
					f.set(ret, null);
				}
			}

			return ret;
		} catch (InstantiationException e) {
			e.printStackTrace();
		} catch (IllegalAccessException e) {
			e.printStackTrace();
		}
		return null;
	}

	/**
	 * Judge whether current tuple is local
	 * 
	 * @Effects: If belong HERE , return true;<br>
	 *           Else, false;
	 */
	public boolean isLocal() {

		if (limeWrapper.cur != null && limeWrapper.cur.isHere()) {
			return true;
		} else {
			return false;
		}
	}
}
